package com.jason.recognition;

import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.Toast;

public class DrawLineActivity extends Activity {
	private DrawView vw;
	ArrayList<Point> mCurrentPointArray;
	ArrayList<Point> mDrawPointArray;
	Recognition mRecoginition;

	private boolean mPressFirstBackKey = false;
	private Timer mTimer;

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		// setContentView(R.layout.main); // 기존 뷰는 사용하지 않는다.

		// 새로운 뷰를 생성하여 액티비티에 연결
		vw = new DrawView(this);
		setContentView(vw);
		mCurrentPointArray = new ArrayList<Point>();
		mDrawPointArray = new ArrayList<Point>();
	}
	
	@Override
	public void onBackPressed() {
		// back key 한번 누르면 이전도형 clear
		// back key연속 두번 누르면 종료처리
		if (mPressFirstBackKey == false) {
			mCurrentPointArray.clear();

			// 마지막 도형의 시작 index를 찾는다.
			int sLastShapePos = 0;
			for (int i = 0; i < mDrawPointArray.size(); i++) {
				if (mDrawPointArray.get(i).mIsStart == true) {
					sLastShapePos = i;
				}
			}
			
			// 마지막 도형을 삭제한다.
			if( mDrawPointArray.size() > 0 )
			{
				for (int i = mDrawPointArray.size()-1; i >= sLastShapePos; i--) {
					mDrawPointArray.remove(mDrawPointArray.get(i));
				}
				vw.invalidate(); // onDraw() 호출
				Toast.makeText(this, "last shape clear", 1).show();
			}
			else
			{
				Toast.makeText(this, "already all shape clear", 1).show();
			}

			mPressFirstBackKey = true;

			// back키가 2초내에 두번 눌렸는지 감지
			TimerTask second = new TimerTask() {
				@Override
				public void run() {
					mTimer.cancel();
					mTimer = null;
					mPressFirstBackKey = false;
				}
			};

			if (mTimer != null) {
				mTimer.cancel();
				mTimer = null;
			}

			mTimer = new Timer();
			mTimer.schedule(second, 800);
		} else {
			super.onBackPressed();	
		}
	}

	public class Point {
		float x;
		float y;
		boolean mIsStart; // 시작점 여부
		float mRadius; // 반지름 : 이 값이 0보다 크면 원이다.

		public Point() {
			this.x = 0;
			this.y = 0;
			this.mRadius = 0;
			this.mIsStart = false;
		}

		public Point(float x, float y, boolean isStart) {
			this.x = x;
			this.y = y;
			this.mRadius = 0;
			this.mIsStart = isStart;
		}
	}

	protected class Recognition {
		Context 		 mContext;
		ArrayList<Point> mPointArray; // 모든 점이 저장됨
		ArrayList<Point> mVertexList; // 꼭지점이 저장됨.

		/*
		 * 각도는 mBasePoint와 현재 점의 각을 구한다. mBasePoint는 6개 이전 점으로 한다.
		 */

		Point mPointSum = new Point();
		double mRadianAvr = 0; // 평균 각도
		int mSumCount = 0; // sum된 개수
		Point mBasePoint; // 각도를 잴 기준 점.

		public Recognition(Context aContext) {
			mContext = aContext;
			
			mPointArray = new ArrayList<Point>();
			mVertexList = new ArrayList<Point>();
			mBasePoint = new Point();
		}

		// 점이 추가될때 호출되는 인터페이스
		public void add(float x, float y, boolean isStart) {
			// mPointArray에 점을 추가
			mPointArray.add(new Point(x, y, isStart));

			// 첫번째 점이면 mBasePoint를 기준점 및 꼭지점으로 설정하고 종료
			if (isStart == true) {
				mBasePoint = mPointArray.get(0);

				mVertexList.add(new Point(x, y, isStart));
				return;
			}

			// 기준점으로부터 6개 이상 넘어가면 기준점을 6개 이전 점으로 설정한다.
			if (mSumCount > 6) {
				mBasePoint = mPointArray.get(mPointArray.size() - 6);
			}

			// mBasePoint와 현재 점의 각도를 구한다.
			float xChangeCur = x - mBasePoint.x;
			float yChangeCur = y - mBasePoint.y;

			// atan2함수를 이용하여 mBasePoint와 현재 점의 각도를 구한다.
			double sRadian = Math.atan2(yChangeCur, xChangeCur);

			//Log.i("test", " sRadian : " + sRadian + " sRadianAvr : " + mRadianAvr);

			// 꼭지점이 생기고 처음 3개는 꼭지점으로 인식하지 않는다.
			if (mSumCount >= 4) {
				// 이번 각도가 평균과 비슷한지 체크한다.
				// Math.PI = 3.14
				// RadianAvr은 0이 0도, PI/2가 90도 PI가 180도, -PI/2가 -90도, -PI가
				// -180도이다.
				// 180도와 -180도는 같은데 다르게 표현됨.
				// 아래에서 0.6은 30~40도 정도 될것 같음.

				if (sRadian < mRadianAvr + 0.6 && sRadian > mRadianAvr - 0.6) {
					// 각도가 비슷함.
					// 꼭지점 아님.
				} else if ((sRadian + Math.PI * 2) < mRadianAvr + 0.6
						&& (sRadian + Math.PI * 2) > mRadianAvr - 0.6) {
					// sRadina에 2pi를 더한것과 비슷함
					// 꼭지점 아님.
				} else if ((sRadian - Math.PI * 2) < mRadianAvr + 0.6
						&& (sRadian - Math.PI * 2) > mRadianAvr - 0.6) {
					// sRadina에 2pi를 뺀것과 비슷함
					// 꼭지점 아님.
				} else {
					// 꼭지점이다.
					// Log.i("test", "find Vertex ");

					// 각도는 꼭지점 부터 다시 재야 함으로 mRadianSum, mSumCount 는 초기화 함.
					mPointSum.x = 0;
					mPointSum.y = 0;
					mSumCount = 0;
					xChangeCur = 0;
					yChangeCur = 0;

					// 이전 point를 base point로 설정한다.
					mBasePoint = mPointArray.get(mPointArray.size() - 2);

					// 이전 point를 꼭지점으로 정한다.
					mVertexList
							.add(new Point(mBasePoint.x, mBasePoint.y, false));
				}
			}

			// 좌표의 합을 구한다.
			mPointSum.x = mPointSum.x + xChangeCur;
			mPointSum.y = mPointSum.y + yChangeCur;

			mSumCount++;

			// 평균 각도를 구한다.
			mRadianAvr = Math.atan2(mPointSum.y, mPointSum.x);
		}

		public void end() {
			// 직선이면 마지막 점을 꼭지점으로 설정
			if (mVertexList.size() == 1) {
				mBasePoint = mPointArray.get(mPointArray.size() - 1);

				mVertexList.add(new Point(mBasePoint.x, mBasePoint.y, false));
			}
			// 직선이 아니면 처음과 끝이 어느정도 가까우면
			// 처음 꼭지점을 마지막 꼭지점으로 입력
			else {
				mBasePoint = mVertexList.get(0);
				Point sLastPoint = mPointArray.get(mPointArray.size() - 1);

				if ((mBasePoint.x - sLastPoint.x > -30 && mBasePoint.x
						- sLastPoint.x < 30)
						&& (mBasePoint.y - sLastPoint.y > -30 && mBasePoint.y
								- sLastPoint.y < 30)) {
					mVertexList
							.add(new Point(mBasePoint.x, mBasePoint.y, false));

					// Log.i("xx", "mVertexList.size " + mVertexList.size());

					// 꼭지점이 5보다 크고 모든 내각이 180보다 작으면 원으로 인식한다.
					// 내각을 체크한다.
					boolean sIsCircle = true;
					float sInnerAngle = 0;

					for (int i = 1; i < mVertexList.size(); i++) {
						Point v1 = new Point();

						if (i == 1) {
							v1.x = mVertexList.get(mVertexList.size() - 2).x
									- mVertexList.get(i - 1).x;
							v1.y = mVertexList.get(mVertexList.size() - 2).y
									- mVertexList.get(i - 1).y;
						} else {
							v1.x = mVertexList.get(i - 2).x
									- mVertexList.get(i - 1).x;
							v1.y = mVertexList.get(i - 2).y
									- mVertexList.get(i - 1).y;
						}

						Point v2 = new Point();
						v2.x = mVertexList.get(i).x - mVertexList.get(i - 1).x;
						v2.y = mVertexList.get(i).y - mVertexList.get(i - 1).y;

						// 두 직선의 내각을 구한다.
						// 내각 = asin(x1y2-y1x2 / ( sqrt(x1*x1 + y1*y1) +
						// sqrt(x2*x2 + y2*y2) )
						double sRadian = Math.asin((v1.x * v2.y - v1.y * v2.x)
								/ (Math.sqrt(v1.x * v1.x + v1.y * v1.y) * Math
										.sqrt(v2.x * v2.x + v2.y * v2.y)));

						// Log.i("check circle", "sRadian " + sRadian);

						if (sRadian > 0) {
							if (i == 1) {
								sInnerAngle = 1;
							} else if (sInnerAngle == -1) {
								sIsCircle = false;
							}
						} else {
							if (i == 1) {
								sInnerAngle = -1;
							} else if (sInnerAngle == 1) {
								sIsCircle = false;
							}

						}

					}

					if (mVertexList.size() > 5 && sIsCircle == true) {
						// 원을 그리기 위해 중심점과 반지름을 구한다.
						Point sMin = new Point();
						Point sMax = new Point();
						Point sCenter = new Point();

						// 원이면 x,y 축 각각 min + max /2 로 중심점을 구한다.

						// 초기값은 첫번째 점
						sMin.x = mPointArray.get(0).x;
						sMin.y = mPointArray.get(0).y;
						sMax.x = mPointArray.get(0).x;
						sMax.y = mPointArray.get(0).y;

						for (int i = 1; i < mPointArray.size(); i++) {
							if (sMin.x > mPointArray.get(i).x) {
								sMin.x = mPointArray.get(i).x;
							}
							if (sMin.y > mPointArray.get(i).y) {
								sMin.y = mPointArray.get(i).y;
							}
							if (sMax.x < mPointArray.get(i).x) {
								sMax.x = mPointArray.get(i).x;
							}
							if (sMax.y < mPointArray.get(i).y) {
								sMax.y = mPointArray.get(i).y;
							}
						}

						sCenter.mIsStart = true;
						sCenter.x = (sMin.x + sMax.x) / 2;
						sCenter.y = (sMin.y + sMax.y) / 2;
						sCenter.mRadius = Math.abs((sCenter.x - sMin.x) / 2
								+ (sCenter.y - sMin.y) / 2);

						mVertexList.clear();

						mVertexList.add(sCenter);
					}
				} else {
					// Log.i("###", "xx: " + (mBasePoint.x - sLastPoint.x) +
					// " yy: " + (mBasePoint.y - sLastPoint.y));
					mVertexList.add(sLastPoint);
				}
			}

			// for(int i=0; i<mVertexList.size(); i++)
			// {
			// Log.i("end", "index " + i +
			// " x " + mVertexList.get(i).x +
			// " y " + mVertexList.get(i).y +
			// " isStart " + mVertexList.get(i).mIsStart);
			// }
			
			
			// mRecoginition.mVertexList가 직선이면 원과 접선을 그린것인지 체크해본다.
			
			if( mVertexList.size() == 2 ) {
				// size가 2이면 직선이다.
				
				// 기존에 그려진 원을 찾는다.
				for (int i = 0; i < mDrawPointArray.size(); i++) {
					final Point sCircle = mDrawPointArray.get(i);
					
					if (sCircle.mRadius > 0) {
						Log.i("접선", "기존원 x:" + sCircle.x + " y " + sCircle.y +
								" r : " + sCircle.mRadius );
						
						// 원의 중심점과 직선의 거리를 구해서 반지름과 비교한다.
						// 점과 직선의 거리 : d = |ax1 + by1 + c| / root(a^2 + b^2)
						// 두점(x1,y1)(x2,y2)을 지나는 직선의 방정식 : y - y1 = (y2 - y1)/(x2 - x1) * (x - x1)
						// 직선의 방정식을 적용하면 시작점과 끝점이 없는 직선이 만들어진다.
						// 우리가 원하는 직선은 시잠점과 끝점이 있는데..
						
						// 그래서 일단 쉽게 직선상의 점과 원의 중심점의 거리의 최소값을 구하는걸로 원의 중심점과 직선의 거리를 구한다.
						double sDistance = 0;
						double sMinDistance = 0;
						Point sStart = mVertexList.get(0);
						Point sEnd = mVertexList.get(1);
						Point sPointOfTangency = new Point();  
						
						// 직선을 100개의 점으로 나눴을때 다음점으로 이동되는 값
						float xChange = (sEnd.x - sStart.x) / 100;
						float yChange = (sEnd.y - sStart.y) / 100;

						// 100개의 점에 대해서 원의 중심점과 거리 구하고 그중에서 최소값을 원과 직선의 거리로 인식한다.
						for( int j=0; j<100; j++ )
						{
							// 두점의 거리 d = root( (x1-x2)^2 + (y1-y2)^2 )
							sDistance = Math.pow(sCircle.x - (sStart.x + xChange * j), 2);
							sDistance += Math.pow(sCircle.y - (sStart.y + yChange * j), 2);
							sDistance = Math.sqrt( sDistance );
							
							if( j == 0 || sDistance < sMinDistance ) 
							{
								// MinDistance에 첫번째 값을 설정한다. 
								// 두번째 부터는 sMinDistance 보다 작은 값이면 sMinDistance를 sDistance로 설정한다.
								sMinDistance = sDistance; 
								sPointOfTangency.x = sStart.x + xChange * j;
								sPointOfTangency.y = sStart.y + yChange * j;
							}
						}
						
						Log.i("접선", "직선과 거리 : " + sMinDistance );
						
						// 원과 직선의 오차가 20 이내면 접선으로 인식
						if( Math.abs(sMinDistance - sCircle.mRadius) < 20 )
						{

							Toast toast = Toast.makeText(mContext, "recognized Tangency", Toast.LENGTH_SHORT); 
							toast.show(); 
							
							Log.i("접선", "after show" );
							
							ProcessTangent(sMinDistance, sCircle, sPointOfTangency);

							// 접선이 인식되었으면 다른 원과의 비교는 종료한다.
						}
					}
				}
			
			}
		}

		// 접선 처리하는 인터페이스
		public void ProcessTangent(double sMinDistance, Point sCircle, Point sPointOfTangency) {
			Point sStart = mVertexList.get(0);
			Point sEnd = mVertexList.get(1);

			// 움직여야 하는 거리(sMove)는 반지름 - 직선과 원중심점 거리이다.
			double sMove = Math.sqrt(Math.pow(sCircle.mRadius - sMinDistance, 2) / 2);

			// 접선 이면 직선을 접선이 되도록 이동
			
			// 원의 중심을 기준으로 접점
			Point sTemp = new Point(sPointOfTangency.x - sCircle.x, sPointOfTangency.y - sCircle.y, false);
			
			// 접점의 각도
			double sRadian = Math.atan2(sTemp.y, sTemp.x);
			
			// 각도가 sRadian이고 대각선이sMove일때 이동해야 하는 x값 
			double sMoveX = Math.cos(sRadian) * sMove;
			
			// 각도가 sRadian이고 대각선이sMove일때 이동해야 하는 y값 
			double sMoveY = Math.sin(sRadian) * sMove;
			
			Log.i("접선", "sMoveX " + sMoveX + " sMoveY " + sMoveY );
			
			if( sMinDistance > sCircle.mRadius )
			{
				// 직선을 원 중심으로 이동
				sStart.x -= sMoveX;
				sEnd.x -= sMoveX;
				sStart.y -= sMoveY;
				sEnd.y -= sMoveY;
			}
			else
			{
				// 직선을 원 바깥으로 이동
				sStart.x += sMoveX;
				sEnd.x += sMoveX;
				sStart.y += sMoveY;
				sEnd.y += sMoveY;
			}
		}
	}

	protected class DrawView extends View {
		Paint mPaint; // 페인트 객체 선언
		Context mContext;

		public DrawView(Context context) {
			super(context);
			
			mContext = context;

			// 페인트 객체 생성 후 설정
			mPaint = new Paint();
			mPaint.setColor(Color.BLACK);
			mPaint.setStrokeWidth(3);
			mPaint.setAntiAlias(true); // 안티얼라이싱
			mPaint.setStyle(Paint.Style.STROKE);
		}

		/** 터치이벤트를 받는 함수 */
		@Override
		public boolean onTouchEvent(MotionEvent event) {
			switch (event.getAction()) {
			case MotionEvent.ACTION_DOWN:
				mCurrentPointArray.add(new Point(event.getX(), event.getY(),
						true));

				mRecoginition = new Recognition(mContext);
				mRecoginition.add(event.getX(), event.getY(), true);
				break;
			case MotionEvent.ACTION_MOVE:
				//Log.i("xx", "xy " + event.getX() + " : " + event.getY() );
				mCurrentPointArray.add(new Point(event.getX(), event.getY(),
						false));
				mRecoginition.add(event.getX(), event.getY(), false);
				break;
			case MotionEvent.ACTION_UP:
				mRecoginition.end();
				mCurrentPointArray.clear();
				mDrawPointArray.addAll(mRecoginition.mVertexList);
				break;
			}

			invalidate(); // onDraw() 호출
			return true;
		}

		/** 화면을 계속 그려주는 함수 */
		@Override
		protected void onDraw(Canvas canvas) {
			super.onDraw(canvas);

			canvas.drawColor(Color.WHITE); // 캔버스 배경색깔 설정

			// 인식된 도형 그리기
			for (int i = 0; i < mDrawPointArray.size(); i++) {
				if (mDrawPointArray.get(i).mIsStart == false) { // 이어서 그리고 있는
																// 중이라면
					canvas.drawLine(mDrawPointArray.get(i - 1).x,
							mDrawPointArray.get(i - 1).y,
							mDrawPointArray.get(i).x, mDrawPointArray.get(i).y,
							mPaint);
					// 이전 좌표에서 다음좌표까지 그린다.
				}
				if (mDrawPointArray.get(i).mRadius > 0) {
					// 원 그리기
					canvas.drawCircle(mDrawPointArray.get(i).x,
							mDrawPointArray.get(i).y,
							mDrawPointArray.get(i).mRadius, mPaint);

				} else {
					// 점만 찍는다.
					canvas.drawPoint(mDrawPointArray.get(i).x,
							mDrawPointArray.get(i).y, mPaint);
				}
			}

			// 현재 터치중인 모양 그리기
			for (int i = 0; i < mCurrentPointArray.size(); i++) {
				// 점만 찍는다.
				canvas.drawPoint(mCurrentPointArray.get(i).x,
						mCurrentPointArray.get(i).y, mPaint);
			}
		}
	}
}